<?php
/**
 * > モデルマッパー
 *
 * モデルに対してデータの更新や格納を行うクラス。
 *
 * LICENSE: Miracle Maple by Mirage is licensed
 * under a Creative Commons 表示-非営利-継承 2.1 日本 License.
 *
 * @category   models
 * @package    maple-story.jp
 * @version    8.0.0
 * @link       http://maple-story.jp/
 * @since      File available since Release 8.0.0
 * @copyright  Copyright 2011 Miracle Maple
 * @license    http://creativecommons.org/licenses/by-nc-sa/2.1/jp/     Creative Commons BY-NC-SA
 */
abstract class Model_Mapper_MapperAbstract
{
    /**
     * @var Zend_Db_Table_Abstract
     */
    protected $_dbTable;
    
    /**
     * キャッシュ
     *
     * @var Zend_Cache
     */
    protected $_cache;
    
    /**
     * ログライター
     *
     * @var Zend_Log
     */
    protected $_logger;
    
    /**
     * モデル名
     *
     * @var string
     */
    protected $_modelName;
    
    /**
     * 初期化処理
     *
     * 基本プロパティおよびキャッシュオブジェクトを設定します。
     */
    public function __construct()
    {
        $this->_logger = Zend_Registry::get('APPLICATION_LOG');
        $this->_cache = Zend_Registry::get('APPLICATION_CACHE');
        $this->_modelName = str_replace('Mapper_', '', get_class($this));
    }

    /**
     * テーブル設定
     *
     * データの操作に用いる Zend_Db_Table インスタンスを作成する。
     * 
     * @param  Zend_Db_Table_Abstract $dbTable 
     * @return Model_MapperAbstract
     */
    public function setDbTable($dbTable)
    {
        if (is_string($dbTable)) {
            $dbTable = new $dbTable();
        }
        if (!$dbTable instanceof Zend_Db_Table_Abstract) {
            throw new Exception('Invalid table data gateway provided');
        }
        $this->_dbTable = $dbTable;
        return $this;
    }

    /**
     * テーブル取得
     *
     * 設定されている Zend_Db_Table インスタンスを取得する。
     * 設定されていない場合は、クラス名から自動的にインスタンスを作成する。
     * 
     * @return Zend_Db_Table_Abstract
     */
    public function getDbTable()
    {
        if (null === $this->_dbTable) {
            $dbTable = str_replace('Mapper', 'DbTable', get_class($this));
            $this->setDbTable($dbTable);
        }
        return $this->_dbTable;
    }

    /**
     * データ保存
     *
     * モデルデータの保存を行います。
     * ID が定義されていない場合は insert、
     * 定義されている場合は update を実行します。
     * 
     * @param  Model_ModelAbstract $model 
     * @return void
     */
    public function save(Model_ModelAbstract $model)
    {
        $data = $model->getColumns();

        if (null === $data['id']) {
            $this->getDbTable()->insert($data);
        } else {
            $this->getDbTable()->update($data, array('id = ?' => $data['id']));
        }
    }

    /**
     * データID検索
     *
     * データをIDで検索してモデルにマッピングします。
     * 
     * @param  int $id 
     * @param  Model_ModelAbstract $model 
     * @return void
     */
    public function find($id)
    {
        // キャッシュを取得
        $cache = $this->_getCache(__FUNCTION__, $id);        
        if ($cache !== false) {                         
            return $cache;
        }

        // データを取得してモデルにマッピング
        $result = $this->getDbTable()->find($id);
        if (!count($result)) {
            return false;
        }
        $row = $result->current();
        $entry = new $this->_modelName();
        $entry->setColumns($row)
              ->setMapper($this);

        // キャッシュを保存
        $this->_setCache($entry, __FUNCTION__, $id);

        return $entry;
    }

    /**
     * 全データ取得
     *
     * 全データを取得して1行ずつモデルに格納し、
     * それぞれのモデルを配列にした物を返します。
     * 
     * @param Zend_Db_Select $select
     * @return array
     */
    public function fetchAll($select = null)
    {
        // キャッシュを取得
        $cache = $this->_getCache(__FUNCTION__, $select);
        if ($cache !== false) {                         
            return $cache;
        }
    
        $resultSet = $this->getDbTable()->fetchAll($select);
        // データが無ければ空を返す
        if (!count($resultSet)) {
            return array();
        }

        // 結果行セットをモデルの配列に変換
        $entries   = array();
        foreach ($resultSet as $row) {
            $entry = new $this->_modelName();
            $entry->setColumns($row)
                  ->setMapper($this);
            $entries[] = $entry;
        }

        // キャッシュを保存
        $this->_setCache($entries, __FUNCTION__, $select);

        return $entries;
    }

    /**
     * 依存テーブルデータ取得
     *
     * 全データを取得して1行ずつモデルに格納し、
     * それぞれのモデルを配列にした物を返します。
     * 
     * @param Zend_Db_Table_Select $select
     * @param Zend_Db_Table_Row $row
     * @return array
     */
    public function fetchAllDependent($select)
    {
        // キャッシュを取得
        $cache = $this->_getCache(__FUNCTION__, $select);
        if ($cache !== false) {                         
            return $cache;
        }
    
        $resultSet = $this->getDbTable()->fetchAll($select);

        // データが無ければエラー
        if (!count($resultSet)) {
            return array();
        }

        // 結果行セットをモデルの配列に変換
        $entries = array();
        $dependentTables = $this->getDbTable()->getDependentTables();
        
        foreach ($resultSet as $row) {
            $entry = new $this->_modelName();
            $entry->setColumns($row)
                  ->setMapper($this);

            // 従属データを取得してモデルのプロパティにセット
            foreach ($dependentTables as $dependentTable) {
                $dependentModelClass = str_replace('DbTable_', '', $dependentTable);
                $dependentModel = new $dependentModelClass;
                $dependentTableName = $dependentModel->getMapper()->getDbTable()->info('name');
                $entry->$dependentTableName = $dependentModel->findDependentRowset($row);
            }
            
            $entries[] = $entry;
        }
        
        // キャッシュを保存
        $this->_setCache($entries, __FUNCTION__, $select);

        return $entries;
    }

    /**
     * 依存テーブルデータ取得
     *
     * 全データを取得して1行ずつモデルに格納し、
     * それぞれのモデルを配列にした物を返します。
     * 
     * @param Zend_Db_Table_Select $select
     * @param Zend_Db_Table_Row $row
     * @return array
     */
    public function findDependentRowset($row)
    {
        // 従属データを取得
        $tableClass = get_class($this->getDbTable());
        $resultSet = $row->findDependentRowset($tableClass, null, $this->_getDefaultSelect());
        $dependentTables = $this->getDbTable()->getDependentTables();

        // 結果行セットをモデルの配列に変換
        $entries = array();
        foreach ($resultSet as $row) {
            $model = str_replace('Mapper_', '', get_class($this));
            $entry = new $model();
            $entry->setColumns($row)
                  ->setMapper($this);

            // 従属モデルの同一関数を呼出しすることにより、
            // 再起的に従属データを取得する。
            // 結果行セットをモデルのプロパティにセット。
            foreach ($dependentTables as $dependentTable) {
                $dependentModelClass = str_replace('DbTable_', '', $dependentTable);
                $dependentModel = new $dependentModelClass;
                $dependentTableName = $dependentModel->getMapper()->getDbTable()->info('name');
                $entry->$dependentTableName = $dependentModel->findDependentRowset($row);
            }

            $entries[] = $entry;
        }

        return $entries;
    }

    /**
     * マスタデータ取得
     *
     * マスタデータを取得して主キーをインデックスとする配列を返します。
     * 
     * @param String         $key    配列のキーとなるカラム名
     * @param String         $value  配列の値となるカラム名
     * @param Zend_Db_Select $select
     * @return array
     */
    public function fetchAllArray($key, $value, $select = null)
    {
        // キャッシュを取得
        $cache = $this->_getCache(__FUNCTION__, $select);
        if ($cache !== false) {                         
            return $cache;
        }
    
        $resultSet = $this->getDbTable()->fetchAll($select);

        // データが無ければ空を返す
        if (!count($resultSet)) {
            return array();
        }
    
        // 結果行セットを連想配列に変換
        $entries   = array();
        foreach ($resultSet as $row) {
            $entries[$row->$key] = $row->$value;
        }
        
        $this->_setCache($entries, __FUNCTION__, $select);

        return $entries;
    }
	
	/**
	 * WID 検索
	 *
	 * @param	array	$wid	WID リスト
	 * @param	boolean	$ignoreDelete	削除データの取り扱い有無
	 * @return	array(wid => Zend_Db_Table_Row)	モンスターデータ [WID キー]
	 */
	public function getByWid($wid = array(), $ignoreDelete = false)
	{
		// WID が空の場合は終了
		if (!count($wid)) {
			return array();
		}
		
		$select = $this->getDbTable()->select();
		$select->where('wid IN (?)', $wid)
			   ->order(array('wid ASC'));

		// 削除データ無視の場合以外は delete_flg を加味
		if (!$ignoreDelete) {
			$select->where('delete_flg = 0');
		}

		$rowset = $this->fetchAll($select);

		// WID をキーにする配列
		$widRow = array();		
		foreach ($rowset as $row) {
			$widRow[$row->wid] = $row;
		}
		return $widRow;
	}
    
    /**
     * キャッシュ取得
     *
     * @param string $func キャッシュ関数名
     * @param string $unique キャッシュ識別子
     * @return string キャッシュ名
     */
    protected function _getCache($func, $unique = null)
    {
        $cacheName = $this->_getCacheName($func, $unique);

        if ($this->_cache->test($cacheName)) {
			// デバッグモードではキャッシュを用いない
            if (Zend_Registry::get('DEBUG_MODE') == true) {
                return false;
            } else {
                return $this->_cache->load($cacheName);
            }
        }
        
        return false;
    }
    
    /**
     * キャッシュ保存
     *
     * @param object $data キャッシュ化するデータ
     * @param string $func キャッシュ関数名
     * @param string $unique キャッシュ識別子
     * @return void
     */
    protected function _setCache($data, $func, $unique = null)
    {
		// デバッグモードではキャッシュを用いない
		if (Zend_Registry::get('DEBUG_MODE') == true) {
			return false;
		}
		
        $cacheName = $this->_getCacheName($func, $unique);

        $this->_cache->save($data,
                            $cacheName,
                            array(__CLASS__, $func)
                            );
    }
    
    /**
     * キャッシュ名取得
     *
     * @param object $data キャッシュ化するデータ
     * @param string $func キャッシュ関数名
     * @param string $unique キャッシュ識別子
     * @return void
     */
    protected function _getCacheName($func, $unique = null)
    {
        $uniqueMd5 = '';
        $tableName = $this->getDbTable()->info('name');
        $funcName = ucfirst(strtolower($func));

        if ($unique instanceof Zend_Db_Select) {
            $unique = md5($unique->__toString());
        }
    
        return $tableName . $funcName . $unique;
    }
	
	/**
	 * 基本となる検索条件
	 *
	 * @param boid
	 * @return Zend_Db_Select
	 */
	protected function _getDefaultSelect()
	{
		$select = $this->getDbTable()
		               ->select();
		return $select;
	}

}
